---@class UIDrive.UserData
---@field Element string | Behaviour | GameObject | Transform
---@field Attr string
---@field Property string
---@field PropTab string[]
---@field Eval fun(value:any,oldValue:any):any
---@field GenericObject GenericObject

---@class UIDrive.BindData
---@field Child table<string,UIDrive.BindData>
---@field BeRegister boolean
---@field Compare boolean 是否判断相等之后通知，如果为false，即无论是否相等都会通知
---@field UserDataList UIDrive.UserData[]

---@class UIDrive.MateTab
---@field Value any 直接赋予的值
---@field Default any 当为nil时，赋予这个值
---@field Min number 当为数字是可用，判断赋值大小
---@field Max number 当为数字是可用，判断赋值大小
---@field Type any 可以传对象，可以传字符串，强制对象类型

local type = type
local debug_traceback = debug.traceback
local xpcall = xpcall
local pairs = pairs
local ipairs = ipairs
local ipairs_r = ipairs_r
local setmetatable = setmetatable
local split = string.split
local table_insert = table.insert
local table_remove = table.remove
local table_concat = table.concat
local table_add_range = table.add_range
local select = select
local StackTable_CreateAndPush = StackTable_CreateAndPush
local StackTable_Pop = StackTable_Pop
local table_is_class_type = table.is_class_type
local unpack = unpack or table.unpack
local getmetatable = getmetatable

---@class UIDrive
UIDrive = class()

---计算式放弃当前计算的返回值
UIDriveEvalWaive = SetTableReadonly({})
local UIDriveEvalWaive = UIDriveEvalWaive
---表示空值
UIDriveNilValue = UIDriveEvalWaive
local UIDriveNilValue = UIDriveNilValue

---@param property string | string[]
---@return string,string[]
local function GetProperty(property)
    if type(property) == "string" then
        return property, split(property, ".")
    else
        return table_concat(property, "."), property
    end
end

local function UseMetatableNewindexFunc(tab, key, value)
    local meta = getmetatable(tab)
    meta.__UIDrive:SetProperty(key, value)
end

---@private
function UIDrive:ctor(uiSelf, tab)
    self._eventDispatcher = EventDispatcher:new()
    self:_Init(uiSelf, tab)
end

function UIDrive:Reuse(...)
    self:_Init(...)
    return self
end

---@param uiSelf
function UIDrive:_Init(uiSelf, tab)
    self.__isUsing = true
    self._table = {}
    ---@type table<string,UIDrive.MateTab>
    self._mateTab = tab
    ---@type UIDrive.BindData
    self._bindData = { Child = {} }
    ---@type UIBase
    self._uiSelf = uiSelf
    ---@type table<GenericObject,string> 用于记录注册的通用对象，在销毁的时候可以顺便删除事件
    self._GenericObjectEventList = {}
    self.data = self:_UseMetatable()
    self.NeedCompare = true

    if uiSelf.VC and uiSelf.VC.UpdateData then
        self:AddListener(UIEventType.UpdateData, uiSelf.VC.UpdateData, uiSelf.VC)
    end

    if uiSelf.VM and uiSelf.VM.UpdateData then
        self:AddListener(UIEventType.UpdateData, uiSelf.VM.UpdateData, uiSelf.VM)
    end

    if tab then
        for k, v in pairs(tab) do
            if type(v) == "table" then
                if v.Value then
                    self:SetProperty(k, v.Value)
                end
            else
                if type(k) == "string" then
                    tab[k] = { Value = v }
                    self:SetProperty(k, v)
                else
                    tab[v] = StaticEmptyTable
                end
            end
        end
    end
end

---设置外部读取的元表
---@private
function UIDrive:_UseMetatable()
    return setmetatable({}, {
        __UIDrive = self,
        __index = self._table,
        __newindex = UseMetatableNewindexFunc
    })
end

---@private
---@param bindTab UIDrive.BindData
---@param property string
function UIDrive:_NoticeData(bindTab, newData, oldData, property, propTab)
    local compare = self.NeedCompare
    if bindTab.BeRegister then
        if not compare or not bindTab.Compare or newData ~= oldData or (newData == nil and oldData == nil) then
            self._eventDispatcher:DispatchEvent(property, newData, oldData)
        end
    end

    if bindTab.Child then
        for k, v in pairs(bindTab.Child) do
            table_insert(propTab, k)
            self:_NoticeData(v, newData and newData[k],
                    oldData and oldData[k], property .. "." .. k, propTab)
            table_remove(propTab)
        end
    end
end

function UIDrive:GetTable()
    return self.data
end

---设置字段并且读出旧数据，同时会刷新面板
---@param pro string | table[] 属性，
---@param value any
---@return any 返回原本的值
function UIDrive:SetProperty(pro, value)
    if not UIDrivePool:IsUsing(self) then
        self:_LogDriveIsDestroy()
        return
    end
    if not self._table then
        return
    end
    local prop, propTab = GetProperty(pro)

    --{{{ 如果赋值为直接赋值根节点则判断根节点是否符合规则
    if not table.is_empty(self._mateTab) and #propTab == 1 then
        local mateTab = self._mateTab[propTab[1]]
        if not mateTab then
            self:_LogErrorData(prop, value, mateTab, "没有声明的值")
            return
        end

        if mateTab.Default ~= nil and value == nil then
            value = mateTab.Default
        end

        if value and mateTab.Type and not table_is_class_type(value, mateTab.Type) then
            self:_LogErrorData(prop, value, mateTab, "类型判断错误")
            return
        end

        if mateTab.Min or mateTab.Max then
            if table_is_class_type(value, "number") then
                if mateTab.Min then
                    value = math.max(value, mateTab.Min)
                end
                if mateTab.Max then
                    value = math.min(value, mateTab.Max)
                end
            else
                self:_LogErrorData(prop, value, mateTab, "非数字类型无法判断大小")
                return
            end
        end

    end
    --}}}

    local dataEle = self._table
    local bindEle = self._bindData
    local lastTab
    for i, v in ipairs(propTab) do
        if bindEle then
            bindEle = bindEle.Child[v]
        end
        lastTab = dataEle
        dataEle = dataEle[v]
        if dataEle == nil and i < #propTab then
            dataEle = {}
            lastTab[v] = dataEle
        end
    end

    local old = dataEle
    lastTab[propTab[#propTab]] = value

    if bindEle then
        local isGenericObject
        if value ~= nil then
            isGenericObject = table_is_class_type(value, GenericObject)
        else
            isGenericObject = table_is_class_type(old, GenericObject)
        end
        if isGenericObject then
            self:_BindingGenericObject(bindEle, value, old, prop)
        end
        self:_NoticeData(bindEle, value, old, prop, propTab)
    end
    self:DispatchEvent(UIEventType.UpdateData)
    return old
end

function UIDrive:SetPropertyWithoutNotify(pro, value)
    if not UIDrivePool:IsUsing(self) then
        self:_LogDriveIsDestroy()
        return
    end
    if not self._table then
        return
    end
    local prop, propTab = GetProperty(pro)

    --{{{ 如果赋值为直接赋值根节点则判断根节点是否符合规则
    if not table.is_empty(self._mateTab) and #propTab == 1 then
        local mateTab = self._mateTab[propTab[1]]
        if not mateTab then
            self:_LogErrorData(prop, value, mateTab, "没有声明的值")
            return
        end

        if mateTab.Default ~= nil and value == nil then
            value = mateTab.Default
        end

        if value and mateTab.Type and not table_is_class_type(value, mateTab.Type) then
            self:_LogErrorData(prop, value, mateTab, "类型判断错误")
            return
        end

        if mateTab.Min or mateTab.Max then
            if table_is_class_type(value, "number") then
                if mateTab.Min then
                    value = math.max(value, mateTab.Min)
                end
                if mateTab.Max then
                    value = math.min(value, mateTab.Max)
                end
            else
                self:_LogErrorData(prop, value, mateTab, "非数字类型无法判断大小")
                return
            end
        end

    end
    --}}}
    local dataEle = self._table
    local lastTab
    for i, v in ipairs(propTab) do
        lastTab = dataEle
        dataEle = dataEle[v]
        if dataEle == nil and i < #propTab then
            dataEle = {}
            lastTab[v] = dataEle
        end
    end
    local old = dataEle
    lastTab[propTab[#propTab]] = value
    return old
end

---先设置全部值再通知
function UIDrive:SetProperties(tabs)
    if not UIDrivePool:IsUsing(self) then
        self:_LogDriveIsDestroy()
        return
    end
    local disList = StackTable_CreateAndPush()
    for k, v in pairs(tabs) do
        local prop, propTab = GetProperty(k)
        local dataEle = self._table
        local bindEle = self._bindData
        local lastTab
        for i, v2 in ipairs(propTab) do
            if bindEle then
                bindEle = bindEle.Child[v2]
            end
            lastTab = dataEle
            dataEle = dataEle[v2]
            if dataEle == nil and i < #propTab then
                dataEle = {}
                lastTab[v2] = dataEle
            end
        end
        local old = dataEle
        local value
        if v ~= UIDriveNilValue then
            value = v
        end

        if not table.is_empty(self._mateTab) and #propTab == 1 then
            local mateTab = self._mateTab[propTab[1]]
            if not mateTab then
                self:_LogErrorData(prop, value, mateTab, "没有声明的值")
                mateTab = StaticEmptyTable
                --return
            end
            if mateTab.Default ~= nil and value == nil then
                value = mateTab.Default
            end
            if value and mateTab.Type and not table_is_class_type(value, mateTab.Type) then
                self:_LogErrorData(prop, value, mateTab, "类型判断错误")
                return
            end
            if mateTab.Min or mateTab.Max then
                if table_is_class_type(value, "number") then
                    if mateTab.Min then
                        value = math.max(value, mateTab.Min)
                    end
                    if mateTab.Max then
                        value = math.min(value, mateTab.Max)
                    end
                else
                    self:_LogErrorData(prop, value, mateTab, "非数字类型无法判断大小")
                    return
                end
            end
        end

        lastTab[propTab[#propTab]] = value
        if bindEle then
            local isGenericObject
            if value ~= nil then
                isGenericObject = table_is_class_type(value, GenericObject)
            else
                isGenericObject = table_is_class_type(old, GenericObject)
            end
            if isGenericObject then
                self:_BindingGenericObject(bindEle, value, old, prop)
            end
            local paramObj = StackTable_CreateAndPush()
            paramObj.BindEle = bindEle
            paramObj.Value = value
            paramObj.Old = old
            paramObj.Prop = prop
            paramObj.PropTab = propTab
            table_insert(disList, paramObj)
        end
    end
    for _, v in ipairs_r(disList) do
        self:_NoticeData(v.BindEle, v.Value, v.Old, v.Prop, v.PropTab)
        StackTable_Pop(v)
    end
    StackTable_Pop(disList)
    self:DispatchEvent(UIEventType.UpdateData)
end

---@private
---@param bindTab UIDrive.BindData
---@param genericObject GenericObject
---@param oldGenericObject GenericObject
function UIDrive:_BindingGenericObject(bindTab, genericObject, oldGenericObject, prop, genericProp)
    genericProp = genericProp or {}
    if bindTab.BeRegister and bindTab.UserDataList then
        for i, v in ipairs(bindTab.UserDataList) do
            v.GenericObject = genericObject
        end
    end
    local genericObjectEventList = self._GenericObjectEventList[prop]
    if genericObjectEventList == nil then
        genericObjectEventList = {}
        self._GenericObjectEventList[prop] = genericObjectEventList
    end
    --只往下寻找一层
    if not bindTab.Child then
        return false
    end
    local hasRegisterEvent = false
    for k, v in pairs(bindTab.Child) do
        table_insert(genericProp, k)
        local namePath = table_concat(genericProp, ".")
        local childHas = self:_BindingGenericObject(v, genericObject, oldGenericObject, prop, genericProp)
        if v.BeRegister or childHas then
            if oldGenericObject then
                if table_is_class_type(oldGenericObject, GenericObject) then
                    oldGenericObject:RemovePropertyListener(namePath, self._GenericObjectEvent, self)
                else
                    LogWarningFormat("此处的绑定类型不统一，老对象不为通用对象，但新对象为通用对象，界面名：%s，属性%s",
                            self._uiSelf.UIName, prop)
                end
                hasRegisterEvent = true
            end
            genericObjectEventList[namePath] = true
            if genericObject then
                genericObject:AddPropertyListener(namePath, self._GenericObjectEvent, self, {
                    SrcProperty = prop,
                    BindTab = bindTab,
                    GenericObject = genericObject,
                })
                hasRegisterEvent = true
            end
        end
        table_remove(genericProp)
    end
    return hasRegisterEvent
end

function UIDrive:ClearData()
    if not UIDrivePool:IsUsing(self) then
        self:_LogDriveIsDestroy()
        return
    end
    local data = StackTable_CreateAndPush()
    local keyTable = self._mateTab
    if table.is_empty(self._mateTab) then
        keyTable = self._bindData.Child
    end
    for k in pairs(keyTable) do
        data[k] = UIDriveNilValue
    end
    self:SetProperties(data)
    StackTable_Pop(data)
end

function UIDrive:GetProperty(pro, defaultValue)
    if not UIDrivePool:IsUsing(self) then
        self:_LogDriveIsDestroy()
        return
    end
    local _, propTab = GetProperty(pro)
    local dataEle = self._table
    for _, v in ipairs(propTab) do
        dataEle = dataEle[v]
        if dataEle == nil then
            return defaultValue
        end
    end
    return dataEle
end

---如果有参数，则通知对应参数，如果没有则刷新全部数据
---@param pro string | string[]
function UIDrive:NoticeProperty(pro)
    if not UIDrivePool:IsUsing(self) then
        self:_LogDriveIsDestroy()
        return
    end
    local compare = self.NeedCompare
    self.NeedCompare = false
    if pro then
        local prop, propTab = GetProperty(pro)
        local dataEle = self._table
        local bindEle = self._bindData
        for _, v in ipairs(propTab) do
            bindEle = bindEle.Child[v]
            if not bindEle then
                return
            end
            dataEle = dataEle and dataEle[v]
        end
        self:_NoticeData(bindEle, dataEle, dataEle, prop, propTab)
    else
        local rootBind = self._bindData.Child
        for i, v in pairs(self._table) do
            if rootBind[i] then
                self:_NoticeData(rootBind[i], v, v, i, { i })
            end
        end
    end
    self.NeedCompare = compare
end

---关于通用对象的，如果通用对象在绑定之前赋值的，那么不会被挂上监听
---@generic T,U
---@param element table 节点
---@param attr string
---@param property T 并不能真的泛型，这里还是要写字符串和索引对象，但是可以用BData来创建索引，方便代码提示
---@param eval fun(value:T,oldValue:T,userData:UIDrive.UserData|U):any
---@param compare boolean 是否缓存
---@param userData U 自定义数据
function UIDrive:Binding(element, attr, property, eval, compare, userData)
    if not UIDrivePool:IsUsing(self) then
        self:_LogDriveIsDestroy()
        return
    end
    if element == nil then
        if table_is_class_type(property, "table") then
            property = GetBData(property)
            property = GetProperty(property)
        end
        LogErrorFormat("界面绑定错误（找不到组件）：界面名：%s，属性：%s", self._uiSelf.UIName, property)
        return
    end
    if attr == nil then
        if table_is_class_type(property, "table") then
            property = GetBData(property)
            property = GetProperty(property)
        end
        LogErrorFormat("界面绑定错误(没有属性)：界面名：%s，属性：%s", self._uiSelf.UIName, property)
        return
    end

    if type(element) == "string" then
        element = self._uiSelf.VC[element]
    end
    userData = userData or {}
    userData.Element = element
    userData.Attr = attr
    userData.Eval = eval
    self:AddListener(property, self:_GetEventFunc(userData), self, userData, compare)
end

---@generic T,U
---@param element table 节点
---@param attr string
---@param property T 并不能真的泛型，这里还是要写字符串和索引对象，但是可以用BData来创建索引，方便代码提示
---@param eval fun(value:T,oldValue:T,userData:UIDrive.UserData|U):any | fun(value:T):any
---@param compare boolean 是否缓存
---@param userData U 自定义数据
function UIDrive:BindingNotNil(element, attr, property, eval, compare, userData)
    userData = userData or {}
    userData._NilWaive = true
    self:Binding(element, attr, property, eval, compare, userData)
end

---@generic T
---@param element table[] 节点
---@param attr string
---@param property T 并不能真的泛型，这里还是要写字符串和索引对象，但是可以用BData来创建索引，方便代码提示
---@param eval fun(index:number,value:T):any
---@param compare boolean 是否缓存
function UIDrive:BindingArray(element, attr, property, eval, compare)
    if not UIDrivePool:IsUsing(self) then
        self:_LogDriveIsDestroy()
        return
    end
    if not eval and not element[1] then
        return
    end

    if type(element) == "string" then
        element = self._uiSelf.VC[element]
    end

    if type(attr) == "string" then
        attr = split(attr, ".")
    end

    if #attr == 1 then
        attr = attr[1]
    else
        local ele = {}
        for _, v in ipairs(element) do
            for i = 1, #attr - 1 do
                v = v[attr[i]]
            end
            table_insert(ele, v)
        end
        attr = attr[#attr]
        element = ele
    end

    local userData = {
        Element = element, Attr = attr, Eval = eval
    }
    self:AddListener(property, self:_GetArrayEventFunc(type(element[1][attr]) == "function", eval), self, userData, compare)
end

function UIDrive:CreateTableData(property, tableObj, prefix, compare, eval, canNil)
    if not UIDrivePool:IsUsing(self) then
        self:_LogDriveIsDestroy()
        return
    end
    prefix = prefix or "UIData"
    if compare == nil then
        compare = true
    end
    local func
    if eval then
        if type(eval) == "table" then
            func = self._TableDataFuncWithEvalObj
        else
            func = self._TableDataFuncWithEval
        end
    else
        func = self._TableDataFunc
    end

    self:AddListener(property, func, self, {
        TableObj = tableObj,
        Prefix = prefix,
        Eval = eval,
        CanNil = canNil
    }, compare)
end

function UIDrive:_TableDataFunc(value, oldValue, node)
    if not UIDrivePool:IsUsing(self) then
        self:_LogDriveIsDestroy()
        return
    end
    local userData = node.UserData
    local tableObj = userData.TableObj
    local prefix = userData.Prefix
    local property = userData.Property
    local canNil = userData.CanNil
    local data
    if value then
        data = tableObj:GetValue(value)
        if not canNil and not data then
            self:_LogTableIDLack(tableObj, property, value)
            return
        end
    end
    if not data and canNil then
        return
    end
    self:SetProperty(prefix, data)
end

function UIDrive:_TableDataFuncWithEval(value, oldValue, node)
    if not UIDrivePool:IsUsing(self) then
        self:_LogDriveIsDestroy()
        return
    end
    local userData = node.UserData
    local tableObj = userData.TableObj
    local prefix = userData.Prefix
    local eval = userData.Eval
    local property = userData.Property
    local canNil = userData.CanNil
    local data
    value = StackTable_CreateAndPush(eval(value, oldValue, node))
    if value[1] == UIDriveEvalWaive then
        StackTable_Pop(value)
        return
    end
    if #value > 0 then
        data = tableObj:GetValue(unpack(value))
        if not canNil and not data then
            self:_LogTableIDLack(tableObj, property, value)
            StackTable_Pop(value)
            return
        end
    end
    StackTable_Pop(value)
    if not data and canNil then
        return
    end
    self:SetProperty(prefix, data)
end

function UIDrive:_TableDataFuncWithEvalObj(value, oldValue, node)
    if not UIDrivePool:IsUsing(self) then
        self:_LogDriveIsDestroy()
        return
    end
    local userData = node.UserData
    local tableObj = userData.TableObj
    local prefix = userData.Prefix
    local eval = userData.Eval
    local property = userData.Property
    local canNil = userData.CanNil
    local data
    value = StackTable_CreateAndPush(eval.Func(eval.Obj, value, oldValue, node))
    if value[1] == UIDriveEvalWaive then
        StackTable_Pop(value)
        return
    end
    if #value > 0 then
        data = tableObj:GetValue(unpack(value))
        if not canNil and not data then
            self:_LogTableIDLack(tableObj, property, value)
            StackTable_Pop(value)
            return
        end
    end
    StackTable_Pop(value)
    if not data and canNil then
        return
    end
    self:SetProperty(prefix, data)
end

---@generic T,O
---@param property T 并不能真的泛型，这里还是要写字符串和索引对象，但是可以用BData来创建索引，方便代码提示
---@param func fun(self:O,value:T,oldValue:T):void
---@param obj O
---@param userData any
---@param compare boolean 是否缓存
function UIDrive:AddListener(property, func, obj, userData, compare, priority)
    if not UIDrivePool:IsUsing(self) then
        self:_LogDriveIsDestroy()
        return
    end
    if table_is_class_type(property, "table") then
        property = GetBData(property)
    end
    local ele = self._bindData
    local prop, propTab = GetProperty(property)
    userData = userData or {}
    userData.Property = prop
    userData.PropTab = propTab
    userData.Func = func
    userData.Obj = obj
    for _, v in ipairs(propTab) do
        if not ele.Child then
            ele.Child = {}
        end
        local t = ele.Child[v]
        if not t then
            t = {}
            ele.Child[v] = t
        end
        ele = t
    end
    ele.BeRegister = true
    if ele.Compare ~= false then
        if compare == nil then
            compare = true
        end
        ele.Compare = compare
    end
    if not ele.UserDataList then
        ele.UserDataList = {}
    end
    table_insert(ele.UserDataList, userData)
    self._eventDispatcher:AddListener(prop, func, obj, userData, priority)

    local data = self:GetProperty(property)
    if data ~= nil then
        func(obj, data, nil, { UserData = userData })
    end
end

---监听一次
---@generic T,O
---@param property T 并不能真的泛型，这里还是要写字符串和索引对象，但是可以用BData来创建索引，方便代码提示
---@param func fun(self:O,value:T,oldValue:T):void
---@param obj O
---@param userData any
---@param compare boolean 是否缓存
function UIDrive:AddListenerOnce(property, func, obj, userData, compare)
    self:AddListener(property, self._ListenerOnceFunc, self, {
        Origin = {
            Func = func,
            Obj = obj,
            UserData = userData
        }
    }, compare)
end

function UIDrive:_ListenerOnceFunc(...)
    if not UIDrivePool:IsUsing(self) then
        self:_LogDriveIsDestroy()
        return
    end
    local args = StackTable_CreateAndPush(...)
    local count = select("#", ...)
    local userData = args[count].UserData
    local dataOrigin = userData.Origin
    args[count] = dataOrigin
    if dataOrigin.Obj then
        dataOrigin.Func(dataOrigin.Obj, unpack(args, 1, count))
    else
        dataOrigin.Func(unpack(args, 1, count))
    end
    StackTable_Pop(args)
    self:RemoveListener(userData.Property, self._ListenerOnceFunc, self, userData)
end

function UIDrive:UnBinding(property)
    self:RemoveListener(property)
end

function UIDrive:RemoveListener(property, func, obj, userData)
    if not UIDrivePool:IsUsing(self) then
        self:_LogDriveIsDestroy()
        return
    end
    self._eventDispatcher:RemoveListener(property, func, obj, userData)
end

function UIDrive:DispatchEvent(property, ...)
    if not UIDrivePool:IsUsing(self) then
        self:_LogDriveIsDestroy()
        return
    end
    self._eventDispatcher:DispatchEvent(property, ...)
end

function UIDrive:Destroy()
    self._eventDispatcher:Clear()
    for k, v in pairs(self._GenericObjectEventList) do
        local go = self:GetProperty(k)
        for namePath, _ in pairs(v) do
            --其他地方可能销毁了对象
            if go and go.RemovePropertyListener then
                go:RemovePropertyListener(namePath, self._GenericObjectEvent, self)
            end
        end
    end
    self._table = nil
    self._bindData = nil
    self._uiSelf = nil
    self._GenericObjectEventList = nil
    self.data = nil
    UIDrivePool:Recycle(self)
end

--{{{ 模板函数

---直接赋值
---@private
function UIDrive:_PropertyValueChange(value, oldValue, node)
    if not UIDrivePool:IsUsing(self) then
        self:_LogDriveIsDestroy()
        return
    end
    node = node.UserData
    if node._NilWaive and value == nil then
        return
    end
    node.Element[node.Attr] = value
end

---表达式赋值
---@private
function UIDrive:_PropertyValueChangeByEvaluation(value, oldValue, node)
    if not UIDrivePool:IsUsing(self) then
        self:_LogDriveIsDestroy()
        return
    end
    node = node.UserData
    if node._NilWaive and value == nil then
        return
    end
    local state, re = xpcall(node.Eval, debug_traceback, value, oldValue, node)
    if state then
        if UIDriveEvalWaive ~= re then
            node.Element[node.Attr] = re
        end
    else
        self:_LogError(node, value, oldValue, re)
    end
end

---有表达式的带对象直接赋值
---@private
function UIDrive:_PropertyValueChangeByEvaluationObj(value, oldValue, node)
    if not UIDrivePool:IsUsing(self) then
        self:_LogDriveIsDestroy()
        return
    end
    node = node.UserData
    if node._NilWaive and value == nil then
        return
    end
    local state, re = xpcall(node.Eval.Func, debug_traceback, node.Eval.Obj, value, oldValue, node)
    if state then
        if UIDriveEvalWaive ~= re then
            node.Element[node.Attr] = re
        end
    else
        self:_LogError(node, value, oldValue, re)
    end
end

---有表达式的数组赋值
---@private
function UIDrive:_ArrayPropertyValueChangeByEvaluation(value, oldValue, node)
    if not UIDrivePool:IsUsing(self) then
        self:_LogDriveIsDestroy()
        return
    end
    node = node.UserData
    if node._NilWaive and value == nil then
        return
    end
    for i, v in ipairs(node.Element) do
        local state, re = xpcall(node.Eval, debug_traceback, i, value, oldValue, node)
        if state then
            if UIDriveEvalWaive ~= re then
                v[node.Attr] = re
            end
        else
            self:_LogErrorArray(node, i, value, oldValue, re)
            break
        end
    end
end

---有表达式的数组函数调用
---@private
function UIDrive:_ArrayPropertyValueChangeByEvaluationWithFunction(value, oldValue, node)
    if not UIDrivePool:IsUsing(self) then
        self:_LogDriveIsDestroy()
        return
    end
    node = node.UserData
    if node._NilWaive and value == nil then
        return
    end
    for i, v in ipairs(node.Element) do
        local re = { xpcall(node.Eval, debug_traceback, i, value, oldValue, node) }
        if re[1] then
            if UIDriveEvalWaive ~= re[2] then
                table_remove(re, 1)
                local state, err = xpcall(v[node.Attr], debug_traceback, v, unpack(re))
                if not state then
                    self:_LogErrorArray(node, i, value, oldValue, err)
                end
            end
        else
            self:_LogErrorArray(node, i, value, oldValue, re[2])
            break
        end
    end
end

---有表达式的带对象的数组直接赋值
---@private
function UIDrive:_ArrayPropertyValueChangeByEvaluationObj(value, oldValue, node)
    if not UIDrivePool:IsUsing(self) then
        self:_LogDriveIsDestroy()
        return
    end
    node = node.UserData
    if node._NilWaive and value == nil then
        return
    end
    for i, v in ipairs(node.Element) do
        local state, re = xpcall(node.Eval.Func, debug_traceback, node.Eval.Obj, i, value, oldValue, node)
        if state then
            if UIDriveEvalWaive ~= re then
                v[node.Attr] = re
            end
        else
            self:_LogErrorArray(node, i, value, oldValue, re)
            break
        end
    end
end

---有表达式的带对象的数组调用
---@private
function UIDrive:_ArrayPropertyValueChangeByEvaluationObjWithFunction(value, oldValue, node)
    if not UIDrivePool:IsUsing(self) then
        self:_LogDriveIsDestroy()
        return
    end
    node = node.UserData
    if node._NilWaive and value == nil then
        return
    end
    for i, v in ipairs(node.Element) do
        local re = { xpcall(node.Eval.Func, debug_traceback, node.Eval.Obj, i, value, oldValue, node) }
        if re[1] then
            if UIDriveEvalWaive ~= re[2] then
                table_remove(re, 1)
                local state, err = xpcall(v[node.Attr], debug_traceback, v, unpack(re))
                if not state then
                    self:_LogErrorArray(node, i, value, oldValue, err)
                end
            end
        else
            self:_LogErrorArray(node, i, value, oldValue, re[2])
            break
        end
    end
end

function UIDrive:_PropertyValueChangeInvokeFuncWithObjEvaluation(value, oldValue, node)
    if not UIDrivePool:IsUsing(self) then
        self:_LogDriveIsDestroy()
        return
    end
    node = node.UserData
    if node._NilWaive and value == nil then
        return
    end
    local re = { xpcall(node.Eval.Func, debug_traceback, node.Eval.Obj, value, oldValue, node) }
    if re[1] then
        if UIDriveEvalWaive ~= re[2] then
            table_remove(re, 1)
            local state, err = xpcall(node.Attr, debug_traceback, node.Element, unpack(re))
            if not state then
                self:_LogError(node, value, oldValue, err)
            end
        end
    else
        self:_LogError(node, value, oldValue, re[2])
    end
end

function UIDrive:_PropertyValueChangeInvokeFuncWithEvaluation(value, oldValue, node)
    if not UIDrivePool:IsUsing(self) then
        self:_LogDriveIsDestroy()
        return
    end
    node = node.UserData
    if node._NilWaive and value == nil then
        return
    end
    local re = { xpcall(node.Eval, debug_traceback, value, oldValue, node) }
    if re[1] then
        if UIDriveEvalWaive ~= re[2] then
            table_remove(re, 1)
            local state, err = xpcall(node.Attr, debug_traceback, node.Element, unpack(re))
            if not state then
                self:_LogError(node, value, oldValue, err)
            end
        end
    else
        self:_LogError(node, value, oldValue, re[2])
    end
end

function UIDrive:_PropertyValueChangeInvokeFunc(value, oldValue, node)
    if not UIDrivePool:IsUsing(self) then
        self:_LogDriveIsDestroy()
        return
    end
    node = node.UserData
    if node._NilWaive and value == nil then
        return
    end
    local state, re = xpcall(node.Attr, debug_traceback, node.Element, value)
    if not state then
        self:_LogError(node, value, oldValue, re)
    end
end

--}}}

---@private
function UIDrive:_GetEventFunc(userData)
    local element = userData.Element
    local attr = userData.Attr
    local eval = userData.Eval
    if type(attr) == "function" then
        if eval then
            if type(eval) == "table" then
                return self._PropertyValueChangeInvokeFuncWithObjEvaluation
            end
            return self._PropertyValueChangeInvokeFuncWithEvaluation
        else
            return self._PropertyValueChangeInvokeFunc
        end
    elseif type(element[attr]) == "function" then
        userData.Attr = element[attr]
        if eval then
            if type(eval) == "table" then
                return self._PropertyValueChangeInvokeFuncWithObjEvaluation
            end
            return self._PropertyValueChangeInvokeFuncWithEvaluation
        else
            return self._PropertyValueChangeInvokeFunc
        end
    else
        if eval then
            if type(eval) == "table" then
                return self._PropertyValueChangeByEvaluationObj
            end
            return self._PropertyValueChangeByEvaluation
        else
            return self._PropertyValueChange
        end
    end
end

function UIDrive:_GetArrayEventFunc(isFunction, eval)
    if isFunction then
        if type(eval) == "table" then
            return self._ArrayPropertyValueChangeByEvaluationObjWithFunction
        end
        return self._ArrayPropertyValueChangeByEvaluationWithFunction
    else
        if type(eval) == "table" then
            return self._ArrayPropertyValueChangeByEvaluationObj
        end
        return self._ArrayPropertyValueChangeByEvaluation
    end
end

---@private
function UIDrive:_GenericObjectEvent(namePath, currentValue, ...)
    if not UIDrivePool:IsUsing(self) then
        self:_LogDriveIsDestroy()
        return
    end
    local node = select(select("#", ...), ...).UserData
    local srcProperty = node.SrcProperty
    local prop, propTab = GetProperty(namePath)
    local tempPropTab = {}
    local bindEle = node.BindTab
    ---@type GenericObject
    local genericObject = node.GenericObject
    for i = 1, #propTab - 1 do
        tempPropTab[i] = propTab[i]
        if not bindEle.Child then
            return
        end
        bindEle = bindEle.Child[propTab[i]]
        if not bindEle then
            return
        end
        if bindEle.BeRegister then
            local value = genericObject:GetProperty(tempPropTab)
            self._eventDispatcher:DispatchEvent(srcProperty .. "." .. table_concat(tempPropTab, "."), value, value)
        end
    end
    if not bindEle.Child then
        return
    end
    local lastValue = propTab[#propTab]
    bindEle = bindEle.Child[lastValue]
    if not bindEle then
        return
    end
    tempPropTab[#propTab] = lastValue
    table.insert(tempPropTab, 0, srcProperty)
    local newValue = node.GenericObject:GetProperty(namePath)
    self:_NoticeData(bindEle, newValue, currentValue, srcProperty .. "." .. prop, tempPropTab)
end

function UIDrive:_LogError(node, value, oldValue, err)
    LogError(err)
    local attrName = node.Attr
    local eleTab = node.Element
    if type(attrName) == "function" then
        attrName = GetFunctionName(eleTab, attrName)
    end
    LogErrorFormat("绑定数据出错，界面：%s，属性：%s，节点：%s，数据：%s，旧数据：%s",
            self._uiSelf.UIName, node.Property, (eleTab and eleTab.name or "不知名") .. "." .. (attrName or "无属性"), 
            var2str(value, nil, 8), var2str(oldValue, nil, 8))
end

function UIDrive:_LogErrorArray(node, index, value, oldValue, err)
    LogError(err)
    local attrName = node.Attr
    local eleTab = node.Element[index]
    if type(attrName) == "function" then
        attrName = GetFunctionName(eleTab, attrName)
    end

    LogErrorFormat("绑定数据出错，界面：%s，属性：%s，节点：%s，索引：%s，数据：%s，旧数据：%s",
            self._uiSelf.UIName, node.Property, (eleTab and eleTab.name or "不知名") .. "." .. attrName, index,
            var2str(value, nil, 8), var2str(oldValue, nil, 8))
end

function UIDrive:_LogErrorData(prop, value, mateTab, msg)
    LogErrorFormat("绑定数据赋值出错，错误：%s，界面：%s，属性：%s，数据：%s,信息表：%s",
            msg, self._uiSelf.UIName, prop, var2str(value, nil, 8), var2str(mateTab, nil, 8))
end

function UIDrive:_LogTableIDLack(tableObj, property, id)
    local tableName = LogGetTableStr(tableObj)
    LogErrorFormat("界面名:%s，属性:%s    相关表数据缺失，表名:%s，缺少ID:%s", self._uiSelf.UIName, tostring(property), tableName, id)
end

function UIDrive:_LogDriveIsDestroy()
    LogErrorFormat("UIDrive已经被销毁但是仍然被调用")
end